package com.btobtob.a.biz.user.controller;

import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.State;
import io.dapr.client.domain.TransactionalStateOperation;
import io.dapr.exceptions.DaprException;
import io.grpc.Status;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

@Slf4j
@RestController
@Tag(name = "state", description = "state")
@RequestMapping("/state")
@Validated
public class StateController {

    public static class MyClass {
        public String message;

        @Override
        public String toString() {
            return message;
        }
    }

    private static final String STATE_STORE_NAME = "statestore";

    private static final String FIRST_KEY_NAME = "myKey";

    private static final String SECOND_KEY_NAME = "myKey2";

    @Operation(summary = "test1", tags = "state")
    @GetMapping(value = "/test1")
    public void test1(String message) throws Exception {
        try (DaprClient client = new DaprClientBuilder().build()) {
            System.out.println("Waiting for Dapr sidecar ...");
//            client.waitForSidecar(10000).block();
            System.out.println("Dapr sidecar is ready.");

            MyClass myClass = new MyClass();
            myClass.message = message;
            MyClass secondState = new MyClass();
            secondState.message = "test message";

            client.saveState(STATE_STORE_NAME, FIRST_KEY_NAME, myClass).block();
            System.out.println("Saving class with message: " + message);

            Mono<State<MyClass>> retrievedMessageMono = client.getState(STATE_STORE_NAME, FIRST_KEY_NAME, MyClass.class);
            System.out.println("Retrieved class message from state: " + (Objects.requireNonNull(retrievedMessageMono.block()).getValue()).message);

            System.out.println("Updating previous state and adding another state 'test state'... ");
            myClass.message = message + " updated";
            System.out.println("Saving updated class with message: " + myClass.message);

            // execute transaction
            List<TransactionalStateOperation<?>> operationList = new ArrayList<>();
            operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
                    new State<>(FIRST_KEY_NAME, myClass, "")));
            operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
                    new State<>(SECOND_KEY_NAME, secondState, "")));

            client.executeStateTransaction(STATE_STORE_NAME, operationList).block();

            // get multiple states
            Mono<List<State<MyClass>>> retrievedMessagesMono = client.getBulkState(STATE_STORE_NAME,
                    Arrays.asList(FIRST_KEY_NAME, SECOND_KEY_NAME), MyClass.class);
            System.out.println("Retrieved messages using bulk get:");
            Objects.requireNonNull(retrievedMessagesMono.block()).forEach(System.out::println);

            System.out.println("Deleting states...");

            System.out.println("Verify delete key request is aborted if an etag different from stored is passed.");
            // delete state API
            try {
                client.deleteState(STATE_STORE_NAME, FIRST_KEY_NAME, "100", null).block();
            } catch (DaprException ex) {
                if (ex.getErrorCode().equals(Status.Code.ABORTED.toString())) {
                    // Expected error due to etag mismatch.
                    System.out.printf("Expected failure. %s%n", ex.getErrorCode());
                } else {
                    System.out.println("Unexpected exception.");
                    throw ex;
                }
            }

            System.out.println("Trying to delete again with correct etag.");
            String storedEtag = Objects.requireNonNull(client.getState(STATE_STORE_NAME, FIRST_KEY_NAME, MyClass.class).block()).getEtag();
            client.deleteState(STATE_STORE_NAME, FIRST_KEY_NAME, storedEtag, null).block();
            // Delete operation using transaction API
            operationList.clear();
            operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.DELETE,
                    new State<>(SECOND_KEY_NAME)));
            client.executeStateTransaction(STATE_STORE_NAME, operationList).block();

            Mono<List<State<MyClass>>> retrievedDeletedMessageMono = client.getBulkState(STATE_STORE_NAME,
                    Arrays.asList(FIRST_KEY_NAME, SECOND_KEY_NAME), MyClass.class);
            System.out.println("Trying to retrieve deleted states:");
            Objects.requireNonNull(retrievedDeletedMessageMono.block()).forEach(System.out::println);

            // This is an example, so for simplicity we are just exiting here.
            // Normally a dapr app would be a web service and not exit main.
            System.out.println("Done");
        }
    }

}
